package zj.xinxin.business.business.articale;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.util.CollectionUtils;
import zj.xinxin.api.moudle.article.api.*;
import zj.xinxin.api.moudle.article.dto.ArticleDto;
import zj.xinxin.api.moudle.article.dto.CategoryDto;
import zj.xinxin.api.moudle.article.dto.TagDto;
import zj.xinxin.api.moudle.article.entity.*;
import zj.xinxin.api.moudle.article.vo.*;
import zj.xinxin.business.business.articale.converter.ArticleConverter;
import zj.xinxin.business.business.articale.converter.CategoryConverter;
import zj.xinxin.business.business.articale.converter.TagConverter;
import zj.xinxin.common.result.PageResult;
import zj.xinxin.mapper.mapper.article.ArticleCategoryReferencedMapper;
import zj.xinxin.mapper.mapper.article.ArticleMapper;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * <p>
 * 文章  服务实现类
 * </p>
 *
 * @author zhoujun
 * @since 2022-04-12
 */
@Service
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements ArticleService {

    @Autowired
    CategoryService categoryService;

    @Autowired
    ArticleCategoryReferencedService categoryReferencedService;

    @Autowired
    ArticleCategoryReferencedMapper categoryReferencedMapper;

    @Autowired
    ArticleTagReferencedService tagReferencedService;

    @Autowired
    TagService tagService;

    @Override
    @Transactional
    public Boolean addArticle(ArticleVo article) {
        // 1. 校验类别信息是否存在
        // 1.1 不存在就创建新的类别
        Long categoryId = saveOrUpdateCategory(article.getCategory());
        // 2. 校验标签信息是否存在
        // 2.1 存在,
        // 2.2 不存在就创建新的标签, 建立文章和标签的对应关系
        List<Long> tagList = saveOrUpdateTagList(article.getTagList());
        // 3。保存文章信息
        ArticleEntity articleEntity = ArticleConverter.INSTANCE.toEntity(article);
        boolean articleRes = this.save(articleEntity);
        if (!articleRes) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return Boolean.FALSE;
        }
        // 4 建立文章和类别的对应关系
        boolean categoryReferenceRes = saveCategoryReferenced(categoryId, articleEntity.getArticleId());

        // 5 建立文章和标签的对应关系
        boolean tagReferenceRes = saveTagReferenced(tagList, articleEntity);

        return categoryReferenceRes && tagReferenceRes;
    }

    @Transactional
    public boolean saveTagReferenced(List<Long> tagList, ArticleEntity articleEntity) {
        List<ArticleTagReferencedEntity> tagReferencedList = tagList.stream()
                .map(tagId -> new ArticleTagReferencedEntity()
                        .setArticleId(articleEntity.getArticleId())
                        .setTagId(tagId))
                .collect(Collectors.toList());
        boolean tagReferenceRes = tagReferencedService.saveOrUpdateBatch(tagReferencedList);
        if (!tagReferenceRes) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

    @Transactional
    public boolean saveCategoryReferenced(Long categoryId, Long articleId) {
        boolean categoryReferenceRes = false;
        if (!Objects.isNull(categoryId)) {
            categoryReferenceRes = categoryReferencedService.save(new ArticleCategoryReferencedEntity()
                    .setArticleId(articleId)
                    .setCategoryId(categoryId));
        }
        return categoryReferenceRes;
    }

    @Transactional
    public Long saveOrUpdateCategory(CategoryVo category) {
        CategoryEntity categoryEntity = CategoryConverter.INSTANCE.toEntity(category);
        boolean categoryRes = categoryService.saveOrUpdate(categoryEntity);
        if (!categoryRes) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return null;
        }
        return categoryEntity.getCategoryId();
    }

    @Transactional
    public List<Long> saveOrUpdateTagList(List<TagVo> tagList) {
        List<TagEntity> tagEntityList = tagList.stream()
                .map(TagConverter.INSTANCE::toEntity)
                .collect(Collectors.toList());
        boolean tagRes = tagService.saveOrUpdateBatch(tagEntityList);
        if (!tagRes) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return Collections.emptyList();
        }
        return tagEntityList.stream()
                .map(TagEntity::getTagId)
                .collect(Collectors.toList());
    }


    @Override
    public ArticleDto getByArticleId(Long articleId) {
        ArticleEntity articleEntity = this.getById(articleId);
        if (Objects.isNull(articleEntity)) {
            return null;
        }
        ArticleDto result = ArticleConverter.INSTANCE.toDto(articleEntity);
        // 文章类别
        CategoryDto categoryDto = getCategoryDto(articleId);
        result.setCategory(categoryDto);
        // 文章标签
        List<TagDto> tagDtoList = getTagDtoList(articleId);
        result.setTagList(tagDtoList);
        return result;
    }

    private CategoryDto getCategoryDto(Long articleId) {
        Long categoryId = categoryReferencedService.getOne(new LambdaQueryWrapper<ArticleCategoryReferencedEntity>()
                .eq(ArticleCategoryReferencedEntity::getArticleId, articleId)).getCategoryId();
        CategoryEntity currentCategory = categoryService.getById(categoryId);
        return CategoryConverter.INSTANCE.toDto(currentCategory);
    }

    private List<TagDto> getTagDtoList(Long articleId) {
        List<Long> tagIdList = tagReferencedService.list(new LambdaQueryWrapper<ArticleTagReferencedEntity>()
                        .eq(ArticleTagReferencedEntity::getArticleId, articleId)).stream()
                .map(ArticleTagReferencedEntity::getTagId)
                .collect(Collectors.toList());
        return tagService.list(new LambdaQueryWrapper<TagEntity>()
                        .in(TagEntity::getTagId, tagIdList)).stream()
                .map(TagConverter.INSTANCE::toDto)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional
    public Boolean updateByVo(ArticleUpdateVo article) {

        Long categoryId = saveOrUpdateCategory(article.getCategory());
        List<Long> tagList = saveOrUpdateTagList(article.getTagList());
        ArticleEntity articleEntity = ArticleConverter.INSTANCE.toEntity(article);
        boolean articleRes = this.updateById(articleEntity);
        if (!articleRes) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return Boolean.FALSE;
        }
        // 4 建立文章和类别的对应关系
        boolean categoryReferenceRes = saveCategoryReferenced(categoryId, articleEntity.getArticleId());

        // 5 建立文章和标签的对应关系
        boolean tagReferenceRes = saveTagReferenced(tagList, articleEntity);

        return categoryReferenceRes && tagReferenceRes;

    }

    @Override
    public PageResult<ArticleDto> searchPage(ArticlePageSearchVo search) {
        // 查询类别名称包含对应的数据
        List<Long> articleIdListByCategory = getArticleIdByCategoryInfo(search.getSearchContent(),
                search.getCategoryVo());
        // 查询标签信息
        List<Long> articleIdListByTag = getArticleIdByTagInfo(search);
        // 查询文章信息
        IPage<ArticleDto> result = getArticleDtoIPage(search, articleIdListByCategory, articleIdListByTag);
        // 设置每个 文章的类别和标签信息
        List<ArticleDto> records = result.getRecords();
        List<Long> articleIdList = records.stream()
                .map(ArticleDto::getArticleId)
                .collect(Collectors.toList());
        // 查找类别信息
        Map<Long, Long> articleAndCategoryIdMap = getArticleAndCategoryIdMap(articleIdList);
        Map<Long, CategoryEntity> categoryMap = getIdAndCategoryMap(articleAndCategoryIdMap);
        // 查找标签信息
        Map<Long, List<Long>> articleAndTagIdMap = getArticleAndTagIdMap(articleIdList);
        Map<Long, TagEntity> tagMap = getIdAndTagMap(articleAndTagIdMap);
        // 设置类别和标签信息
        setCategoryAndTagInfo(records, articleAndCategoryIdMap, categoryMap, articleAndTagIdMap, tagMap);

        return new PageResult<>(records, result.getTotal());
    }

    private void setCategoryAndTagInfo(List<ArticleDto> records,
                                       Map<Long, Long> articleAndCategoryIdMap,
                                       Map<Long, CategoryEntity> categoryMap,
                                       Map<Long, List<Long>> articleAndTagIdMap,
                                       Map<Long, TagEntity> tagMap) {
        records.forEach(articleDto -> {
            // 设置类别
            Long categoryId = articleAndCategoryIdMap.get(articleDto.getArticleId());
            CategoryEntity categoryEntity = categoryMap.get(categoryId);
            CategoryDto categoryDto = CategoryConverter.INSTANCE.toDto(categoryEntity);
            articleDto.setCategory(categoryDto);
            // 设置标签
            List<Long> tagIdList = articleAndTagIdMap.get(articleDto.getArticleId());
            List<TagDto> tagList = tagIdList.stream()
                    .map(tagMap::get)
                    .map(TagConverter.INSTANCE::toDto)
                    .collect(Collectors.toList());
            articleDto.setTagList(tagList);
        });
    }

    private IPage<ArticleDto> getArticleDtoIPage(ArticlePageSearchVo search,
                                                 List<Long> articleIdListByCategory,
                                                 List<Long> articleIdListByTag) {
        return this.page(new Page<>(search.getCurrent(), search.getSize()),
                        new LambdaQueryWrapper<ArticleEntity>()
                                .le(!Objects.isNull(search.getPublishDateEnd()), ArticleEntity::getPushData, search.getPublishDateEnd())
                                .ge(!Objects.isNull(search.getPublishDateStart()), ArticleEntity::getPushData, search.getPublishDateStart())
                                .and(!CollectionUtils.isEmpty(articleIdListByCategory) ||
                                        !CollectionUtils.isEmpty(articleIdListByTag), wrapper ->
                                        wrapper.in(!CollectionUtils.isEmpty(articleIdListByCategory),
                                                        ArticleEntity::getArticleId, articleIdListByCategory)
                                                .or(!CollectionUtils.isEmpty(articleIdListByTag))
                                                .in(ArticleEntity::getArticleId, articleIdListByTag))
                                .and(StringUtils.isNotEmpty(search.getSearchContent()), wrapper ->
                                        wrapper.like(ArticleEntity::getTitle, search.getSearchContent())
                                                .or()
                                                .like(ArticleEntity::getArticleSummary, search.getSearchContent())
                                                .or()
                                                .like(ArticleEntity::getContentMd, search.getSearchContent())))
                .convert(ArticleConverter.INSTANCE::toDto);
    }

    private Map<Long, TagEntity> getIdAndTagMap(Map<Long, List<Long>> articleAndTagIdMap) {
        if (Objects.isNull(articleAndTagIdMap) || CollectionUtils.isEmpty(articleAndTagIdMap)){
            return Collections.emptyMap();
        }
        List<Long> tagIdList = articleAndTagIdMap.values()
                .stream()
                .flatMap(Collection::stream)
                .distinct()
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(tagIdList)){
            return Collections.emptyMap();
        }
        return tagService.list(new LambdaQueryWrapper<TagEntity>()
                        .in(!CollectionUtils.isEmpty(tagIdList),
                                TagEntity::getTagId, tagIdList))
                .stream()
                .collect(Collectors.toMap(TagEntity::getTagId, Function.identity()));
    }

    private Map<Long, List<Long>> getArticleAndTagIdMap(List<Long> articleIdList) {
        return tagReferencedService.list(new LambdaQueryWrapper<ArticleTagReferencedEntity>()
                        .in(!CollectionUtils.isEmpty(articleIdList),
                                ArticleTagReferencedEntity::getArticleId, articleIdList))
                .stream()
                .collect(Collectors.groupingBy(ArticleTagReferencedEntity::getArticleId,
                        Collectors.mapping(ArticleTagReferencedEntity::getTagId, Collectors.toList())));
    }

    private Map<Long, CategoryEntity> getIdAndCategoryMap(Map<Long, Long> articleAndCategoryIdMap) {
        if (Objects.isNull(articleAndCategoryIdMap) || CollectionUtils.isEmpty(articleAndCategoryIdMap.values())){
            return Collections.emptyMap();
        }
        return categoryService.list(new LambdaQueryWrapper<CategoryEntity>()
                        .in(CategoryEntity::getCategoryId, articleAndCategoryIdMap.values()))
                .stream()
                .collect(Collectors.toMap(CategoryEntity::getCategoryId, Function.identity()));
    }

    private Map<Long, Long> getArticleAndCategoryIdMap(List<Long> articleIdList) {
        return categoryReferencedService.list(
                        new LambdaQueryWrapper<ArticleCategoryReferencedEntity>()
                                .in(!CollectionUtils.isEmpty(articleIdList),
                                        ArticleCategoryReferencedEntity::getArticleId, articleIdList))
                .stream()
                .collect(Collectors.toMap(ArticleCategoryReferencedEntity::getArticleId, ArticleCategoryReferencedEntity::getCategoryId));
    }

    private List<Long> getArticleIdByTagInfo(ArticlePageSearchVo search) {
        List<Long> tageIdList = null;
        if (StringUtils.isNotEmpty(search.getSearchContent())) {
            tageIdList = tagService.list(new LambdaQueryWrapper<TagEntity>()
                            .eq(TagEntity::getTagName, search.getSearchContent())
                            .or()
                            .eq(TagEntity::getAliasName, search.getSearchContent())
                            .or()
                            .eq(TagEntity::getDescription, search.getSearchContent()))
                    .stream()
                    .map(TagEntity::getTagId)
                    .collect(Collectors.toList());
        }
        List<Long> tageIdSearchList = null;
        if (!CollectionUtils.isEmpty(search.getTagList())) {
            tageIdSearchList = search.getTagList().stream()
                    .map(TagVo::getTagId)
                    .collect(Collectors.toList());
        }
        if (CollectionUtils.isEmpty(tageIdList) && CollectionUtils.isEmpty(tageIdSearchList)) {
            return Collections.emptyList();
        }
        return tagReferencedService.list(new LambdaQueryWrapper<ArticleTagReferencedEntity>()
                        .in(!CollectionUtils.isEmpty(tageIdList),
                                ArticleTagReferencedEntity::getTagId, tageIdList)
                        .or(!CollectionUtils.isEmpty(tageIdSearchList))
                        .in(ArticleTagReferencedEntity::getTagId, tageIdSearchList))
                .stream()
                .map(ArticleTagReferencedEntity::getArticleId)
                .collect(Collectors.toList());
    }

    private List<Long> getArticleIdByCategoryInfo(String categoryName, CategoryVo categoryVo) {
        if (Objects.isNull(categoryVo)) {
            return Collections.emptyList();
        }
        List<Long> categoryIdList = null;
        if (!Objects.isNull(categoryName)) {
            categoryIdList = categoryService.list(new LambdaQueryWrapper<CategoryEntity>()
                            .like(CategoryEntity::getCategoryName, categoryName))
                    .stream()
                    .map(CategoryEntity::getCategoryId)
                    .collect(Collectors.toList());
        }

        return categoryReferencedService.list(new LambdaQueryWrapper<ArticleCategoryReferencedEntity>()
                        .in(!CollectionUtils.isEmpty(categoryIdList),
                                ArticleCategoryReferencedEntity::getCategoryId, categoryIdList)
                        .or(!Objects.isNull(categoryVo.getCategoryId()) && categoryVo.getCategoryId() > 0)
                        .eq(!Objects.isNull(categoryVo.getCategoryId()) && categoryVo.getCategoryId() > 0,
                                ArticleCategoryReferencedEntity::getCategoryId, categoryVo.getCategoryId()))
                .stream()
                .map(ArticleCategoryReferencedEntity::getArticleId)
                .collect(Collectors.toList());
    }
}
